package com.spacing.app;

import java.util.*;

import android.util.Log;

import com.spacing.app.data.Word;

/*
 * @author Tom
 * @	
 */

public class Spacer {
	// Spacing model variables
	double c = 0.25; // decay scale array to start with
	boolean firstSession = true;

	
	// Lists
	SchemaHelper sh;
	ArrayList<Word> words;
	
	int last_word_seen; //index of last seen word
	boolean last_answer_correct;
	long start_of_event_time;

	// Old structure
	String[] ListL1;
	String[] ListL2;
	int[] wordIds;

	int[] kActivations; // number of activations per index (per pair)
	int[] kActivationsOlderSessions; // number of activations per index (per
										// pair) in earlier sessions

	boolean is_correct;
	
	double[][] decayRates;
	double[][] arrAlpha;
	double[][] arrEnterPress;
	long[][] arrActivationsTimeWordOrderedWithOlderSessions;
	double[] arrAlphaOlderSessions;
	double[][] arrFirstKeyPressWithOlderSessions;
	double[][] arrActivationsOnRtWithOlderSessions;
	double[][] arrActivationValueWithOlderSessions;
	double[][] arrDecayValuesRecentAlpha;

	String[] arrCorrect;
	int[] arrWordOrder;

	int numberOfWordsCorrect;
	int numberOfWordsIncorrect;
	int numberOfPairsShown;

	// Learning method
	String learningMethod = "Spacing";

	// Time variables
	int sessionTime; // length of a session in minutes
	long startOfSessionTime;
	long endOfSessionTime;
	long startOfEventTime;
	long endOfEventTime;
	int durationExperiment = 30; // in minutes
	long psychologicalTime = 0;

	int numberOfRehearsels;
	int numberOfWords;

	// variables for the personal alpha adjustment:
	double[] oldAlphas;
	int minAlphaNumber = 10;
	double standardAlpha;

	double threshold = -0.8;
	

	/**************************************** Still TO DO ********************************************/
	// DOING functie die daadwerkelijk het spacing algoritme gebruikt en woorden
	// laat zien.

	/*
	 * roep je aan bij het starten van een activiteit deze functie initialiseert
	 * de variabelen en leest de oude activivaties in
	 */
	public Spacer(ArrayList<Word> words, boolean firstSession, SchemaHelper sh) {
		this.sh = sh;
		this.words = words;
		// set standard alpha
		standardAlpha = 0.32;
		// initialize the variables
		initVariables();
		// place old results if they are available
		placeOldResults(words, firstSession);
	}

	private void placeOldResults(ArrayList<Word> words, boolean firstSession) {
		// Only place results if there are older sessions
		if (!firstSession) {
			for (int i = 0; i < words.size(); i++) {
				// TODO get the decay values, activations and alpha of earlier
				// sessions
			}
		}
	}

	/*
	 * Gaat alle woorden af en vult de arrays kActivations,
	 * kActivationsOlderSessions en vult de decay rates
	 */
	private void initVariables() {
		
		kActivations = new int[words.size()];
		startOfSessionTime = System.currentTimeMillis();
		numberOfRehearsels = 0;
		numberOfWords = words.size();
		startOfEventTime = startOfSessionTime;
		endOfEventTime = startOfSessionTime;
		decayRates = new double[words.size()][];
		arrAlpha = new double[words.size()][];

		
		
		
		kActivationsOlderSessions = new int[words.size()]; // number of
															// encounters of
															// each word in
															// earlier sessions
		
		
		
		arrActivationsTimeWordOrderedWithOlderSessions = new long[words.size()][]; // times
																					// on
																					// which
																					// the
																					// word
																					// was
																					// seen
		
		arrAlphaOlderSessions = new double[words.size()]; // alpha value of
															// earlier sessions
		arrDecayValuesRecentAlpha = new double[words.size()][];
		arrActivationValueWithOlderSessions = new double[words.size()][];

		numberOfWordsCorrect = 0; // leuk om tijdens een sessie bij te houden.
		numberOfWordsIncorrect = 0;

		int size = 100; // willekeurig gekozen getal. Is nodig om een array
						// lengte aan te geven.
		for (int i = 0; i < words.size(); i++) {

			arrAlpha[i] = new double[size];

			arrActivationsTimeWordOrderedWithOlderSessions[i] = new long[size * 10];
			arrDecayValuesRecentAlpha[i] = new double[size * 10];
			arrActivationValueWithOlderSessions[i] = new double[size*10];

			arrAlpha[i][0] = 0.25;
			kActivations[i] = 0;
			kActivationsOlderSessions[i] = 0;

		}
	}
	
	//wordt nog niet aangeroepen
	private void updateStudyEvent(int index, long time){
		long activation_time = startOfEventTime - startOfSessionTime + psychologicalTime;
		if((kActivations[index] + kActivationsOlderSessions[index]) == 0) {
			
			double alpha_val = standardAlpha;
			arrAlpha[index][kActivations[index]] = alpha_val;
			arrActivationsTimeWordOrderedWithOlderSessions[index][kActivations[index]+kActivationsOlderSessions[index]] = activation_time ;
			calculateNewDecayValues(index,arrAlpha[index][kActivations[index]]);
			
			//code overgenomen van hedderik, na grondig kijken blijkt dat de if statement hieronder dubbel is.
			
			//if((kActivations[index] +kActivationsOlderSessions[index]) == 0){  
				arrActivationValueWithOlderSessions[index][(kActivations[index]+ kActivationsOlderSessions[index])] = -1000;
				arrActivationsOnRtWithOlderSessions[index][(kActivations[index]+kActivationsOlderSessions[index])]= -1000;
//			}else{
//				double activation= getSpacingActivation(index, ((activation_time)/ 1000), arrAlpha[index][kActivations[index]]); 
//				arrActivationValueWithOlderSessions[index][(kActivations[index]+ kActivationsOlderSessions[index])] = activation;
//				//arrActivationsOnRtWithOlderSessions[index][(kActivations[index]+kActivationsOlderSessions[index])]= rt_to_activation(1000);
//			
//			}
		kActivations[index] = kActivations[index] + 1; //increase the number of encounters
		}
		if((kActivations[index]+kActivationsOlderSessions[index]) == 1) {
			//eerste keer study wordt meteen gevolgd door een rehearsal
		}else{
			//study niet de eerste keer, dan verder met de volgende
		}
		
	}

	public void updateAnswer(boolean isCorrect, long time) {
		long activation_time;
		
		if(!isCorrect && kActivations[last_word_seen]!=0){ // probeersel om er voor te zorgen dat bij een fout antwoord de vorige encounter wordt verwijderd
			kActivations[last_word_seen]--;
		}
		int n_activations = kActivations[last_word_seen];
		int n_activations_old = kActivationsOlderSessions[last_word_seen];
		
		last_answer_correct = isCorrect;
		if(isCorrect){
			activation_time = time - startOfSessionTime + psychologicalTime;
			numberOfWordsCorrect++;
		}
		else{
			activation_time = start_of_event_time - startOfSessionTime  + psychologicalTime;
			numberOfWordsIncorrect++;
		}
		
		arrActivationsTimeWordOrderedWithOlderSessions[last_word_seen][n_activations+n_activations_old] = activation_time;
		double temp_alpha = getAlpha(last_word_seen, (activation_time/1000), standardAlpha);
		arrAlpha[last_word_seen][n_activations] = temp_alpha;
		calculateNewDecayValues(last_word_seen, arrAlpha[last_word_seen][n_activations]);
		double acti = getSpacingActivation(last_word_seen, (activation_time/1000), arrAlpha[last_word_seen][n_activations]);
		
		if((n_activations + n_activations_old) == 0){  
			arrActivationValueWithOlderSessions[last_word_seen][(n_activations+ n_activations_old)] = -1000;
			//arrActivationsOnRtWithOlderSessions[last_word_seen][(kActivations[last_word_seen]+kActivationsOlderSessions[last_word_seen])]= -1000;
		}else{
		arrActivationValueWithOlderSessions[last_word_seen][n_activations+n_activations_old] = acti;
		}
		//Set ready for next rehearsal
		
		kActivations[last_word_seen]++;
		

		//sh.addEncounter(words.get(last_word_seen).getWord1(), (activation_time+startOfSessionTime));
		
		
		
		//Klaar en nu verder
		if(isCorrect){
			//Volgende!
		}else{
			//Current pair nogmaals leren
		}
		
	}// updateAnswer
	//calculates optimal alpha
	private double getAlpha(int index, long activation_time, double alpha) {
		double result = alpha;
		//TODO calculate optimal alpha 
		
		return result;
	}

	/*
	 * public void showWordPair() {
	 * 
	 * }// showWordPair
	 * 
	 * public void presentWordPair() {
	 * 
	 * }// presentWordPair
	 * 
	 * public void rehearseWordPair() {
	 * 
	 * }// rehearseWordPair
	 */
	public void storeRehearseEvent() {

	}// storeRehearseEvent

	public void getFirstKeyPress() {

	}// getFirstKeyPress

	public void displayCorrect() {
		// elders
	}// displayCorrect

	public void removeFeedback() {
		// elders
	}// removeFeedback

	public void displayIncorrect() {
		// elders
	}// displayIncorrect

	public void sessionEnded() {

	}// sessionEnded

	public void saveData() {

	}// saveData

	// wanneer je een nieuw item krijgt, krijg je het alleen een bepaalde tijd
	// te zien zodat je het kan leren
	public void storeStudyEvent() {

	}// storeStudyEvent

	public int getCurrentWordPair() {
		return -1;
	}// getCurrentWordPair

	public boolean isCorrectResponse() {
		// elders
		return false;
	}// isCorrectResponse

	public void stopExp() {
		// wanneer je voortijdig stopt of het programma verlaat wil je zoveel
		// mogelijk dat behouden. We moeten gaan uitzoeken wat we af kunnen
		// vangen.
	}// stopExp

	/*
	 * ========================================= Design / layout functions zijn
	 * waarschijnlijk niet allemaal nodig
	 * 
	 * f
	 * 
	 * ==========================================
	 */

	public void updateFeedbackOfWordList() {

	}// updateFeedbackOfWordList

	/*************************************** END Still TO DO ******************************************/

	/*
	 * Practical functions
	 */

	// set all variables
	public void resetVariables() {

		sessionTime = durationExperiment;
		int size = 15 * Math.round(sessionTime + 1); // CHECK: round lijkt
														// overbodig
		Date currentTime = new Date();
		kActivations = new int[ListL1.length];
		startOfSessionTime = currentTime.getTime();
		endOfSessionTime = startOfSessionTime = durationExperiment * 60 * 1000;
		numberOfRehearsels = 0;
		numberOfWords = ListL1.length;
		startOfEventTime = startOfSessionTime;
		endOfEventTime = startOfSessionTime;
		decayRates = new double[ListL1.length][];
		arrAlpha = new double[ListL1.length][];
		arrEnterPress = new double[ListL1.length][];

		kActivationsOlderSessions = new int[ListL1.length]; // number of
															// encounters of
															// each word in
															// earlier sessions
		arrActivationsTimeWordOrderedWithOlderSessions = new long[ListL1.length][]; // times
																					// on
																					// which
																					// the
																					// word
																					// was
																					// seen
		arrAlphaOlderSessions = new double[ListL1.length]; // alpha value of
															// earlier sessions
		arrFirstKeyPressWithOlderSessions = new double[ListL1.length][];
		arrActivationsOnRtWithOlderSessions = new double[ListL1.length][];
		arrActivationValueWithOlderSessions = new double[ListL1.length][];
		arrDecayValuesRecentAlpha = new double[ListL1.length][];

		numberOfWordsCorrect = 0;
		numberOfWordsIncorrect = 0;

		// Variables spacing model
		for (int i = 0; i < ListL1.length; i++) {

			arrAlpha[i] = new double[size];
			arrEnterPress[i] = new double[size];

			arrActivationsTimeWordOrderedWithOlderSessions[i] = new long[size * 10];
			arrFirstKeyPressWithOlderSessions[i] = new double[size * 10];
			arrActivationsOnRtWithOlderSessions[i] = new double[size * 10];
			arrActivationValueWithOlderSessions[i] = new double[size * 10];
			arrDecayValuesRecentAlpha[i] = new double[size * 10];

			arrAlpha[i][0] = 0.25;
			kActivations[i] = 0;
			kActivationsOlderSessions[i] = 0;

		}

	}// resetVariables

	/*
	 * Determines the constant parameter alpha for the decay function
	 */
	public void setStandardAlpha() {

		if (oldAlphas != null) {
			if (oldAlphas.length > minAlphaNumber) {

				standardAlpha = getMedian(oldAlphas);
				if (standardAlpha < 0.28) { // standardAlpha below 0.28 will
											// cause the words to be spaced too
											// much at the start.

					standardAlpha = 0.28;
				}

			} else {

				standardAlpha = 0.32;
			}

		} else {

			standardAlpha = 0.32;

		}
	}// setStandardAlpha

	double getMedian(double[] values) { // calculate median Alpha value (or any
										// other median)

		double median;
		double[] sortedValues;

		Arrays.sort(values);
		sortedValues = values;

		if (sortedValues.length % 2 == 0) {
			median = Math
					.round(((sortedValues[(sortedValues.length / 2) - 1] + sortedValues[sortedValues.length / 2]) / 2) * 1000) / 1000;
		} else {

			int index = (sortedValues.length - 1) / 2;
			median = sortedValues[index];
		}

		return median;
	}// getMedian

	public void randomizeList() { // randomize two word lists ensuring the pairs
									// still match

		String wordL1;
		String wordL2;
		int wordId;
		int randomIndex;

		for (int i = 0; i < ListL1.length; i++) {
			randomIndex = (int) Math.floor((ListL1.length * Math.random()));
			wordL1 = ListL1[i]; // swap thes word pairs, with random index
								// number
			wordL2 = ListL2[i];
			wordId = wordIds[i];
			ListL1[i] = ListL1[randomIndex];
			ListL2[i] = ListL2[randomIndex];
			wordIds[i] = wordIds[randomIndex];

			ListL1[randomIndex] = wordL1;
			ListL2[randomIndex] = wordL2;
			wordIds[randomIndex] = wordId;
		}

	}// randomizeList

	// TODO levenshteinDistance
	// public static int levenshteinDistance(String word1, String word2){
	// //calculate levenshtein distance between two strings
	//
	// }//levenshteinDistance

	/*
	 * Real model functions
	 */
	// function to recalculate new decay values after a pair has been seen
	public void calculateNewDecayValues(int index, double alpha) {
		
		double [] activations;
		if((kActivationsOlderSessions[index] + kActivations[index]) > 0){
			activations = new double[(kActivationsOlderSessions[index] + kActivations[index])+1];
		}else{
			activations = new double[1];
		}
		
		double[] encounters = new double[(kActivationsOlderSessions[index] + kActivations[index])];

		for (int i = 0; i <= (kActivationsOlderSessions[index] + kActivations[index]); i++) {
			if (i > 0) {
				
				
					activations[i] = getActivation(
							encounters,
							(arrActivationsTimeWordOrderedWithOlderSessions[index][i] / 1000),
							arrDecayValuesRecentAlpha[index], i);
					if (i<encounters.length) {
						encounters[i] = (arrActivationsTimeWordOrderedWithOlderSessions[index][i] / 1000);
					}
					arrDecayValuesRecentAlpha[index][i] = get_decay(
							activations[i], alpha);
				

			} else {
				
					activations[i] = -1000;
					arrDecayValuesRecentAlpha[index][i] = get_decay(
							activations[i], alpha);
				if (encounters.length != 0) {
					encounters[i] = (arrActivationsTimeWordOrderedWithOlderSessions[index][i] / 1000);
				}
			}
		}
	} // calculateNewDecayValues

	// function to restore the decay values from older sessions and place them
	// in the current variable
	public void restoreDecayValues(int index, double alpha) {
		double[] activations = new double[(kActivationsOlderSessions[index] + kActivations[index])];
		double[] encounters = new double[(kActivationsOlderSessions[index] + kActivations[index])];

		for (int i = 0; i < (kActivationsOlderSessions[index] + kActivations[index]); i++) {
			if (i > 0) {
				activations[i] = getActivation(
						encounters,
						(arrActivationsTimeWordOrderedWithOlderSessions[index][i] / 1000),
						arrDecayValuesRecentAlpha[index], i);
				encounters[i] = (arrActivationsTimeWordOrderedWithOlderSessions[index][i] / 1000);
				arrDecayValuesRecentAlpha[index][i] = get_decay(activations[i],
						alpha);

			} else {
				activations[i] = -1000;
				arrDecayValuesRecentAlpha[index][i] = get_decay(activations[i],
						alpha);
				encounters[i] = (arrActivationsTimeWordOrderedWithOlderSessions[index][i] / 1000);

			}
		}

	}


	/*
	 * SpacingActivation returns recent activation value of a pair uses
	 * activation times and decay rates
	 * 
	 * @param: wordnumber index, current time
	 */
	double getSpacingActivation(int index, long time, double alpha) {

		double activationValue = 0;
		double sum = 0;

		double[] encounters = new double[(kActivationsOlderSessions[index] + kActivations[index])];

		for (int i = 0; i < (kActivationsOlderSessions[index] + kActivations[index]); i++) {
			encounters[i] = (arrActivationsTimeWordOrderedWithOlderSessions[index][i] / 1000);

			sum = sum
					+ Math.pow((time - encounters[i]),
							-arrDecayValuesRecentAlpha[index][i]);//TODO STUK

		}

		activationValue = Math.log(sum);
		return activationValue;

	}// getSpacingActivation

	/*
	 * calculate activation value based on the timestamps of encounters, current
	 * time, decay values returns an activation value an item for the current
	 * time (time), high activation means that recall will be accurate and fast.
	 * Activation is based on the summation of the individual practive events. -
	 * encounter contains an array of timestamps on which the item was
	 * encountered - time is the current time - decays contains an array of
	 * decay values for each encounter - s is the index: if s is 2 activation is
	 * calculated for the second encounter
	 * 
	 * 
	 */
	double getActivation(double[] encounters, long time, double[] decays, int s) { // all
																					// time
																					// units
																					// in
																					// seconds

		double sum = 0;
		double activation = 0;

		for (int i = 0; i < s; i++) {
			sum = (sum + Math.pow(((time - encounters[i])), -decays[i]));
		}
		activation = Math.log(sum);

		return activation;

	}// getActivation

	// calculate decay value based on the activation and the alfa value
	// (constant and also the minimum decay value)
	double get_decay(double activation, double alpha) {
		double decay = c * Math.exp(activation) + alpha;
		return decay;

	}// get decay

	/*
	 * determines the next pair looks for the the pair with the lowest
	 * activation (excluding new pairs) based on the lookahead time the methods
	 * checks if the activation will be below the threshold. if so then this is
	 * the next pair. if none of the previously seen pairs comes below the
	 * threshold and there are new pairs then select a new pair. If everything
	 * is seen at least once, then select the pair with the oldest repetition
	 */
	int determineNextPair() {
		int nextPair = 0;
		int lookAhead = 15; // look ahead time in seconds. Lookahead to see what
							// the activation will 15 seconds from now
		double lowestActivation = Double.MAX_VALUE;
		long time; // current Time
		double activationValue;
		double temp_alpha;
		int index = 0;

		/*************************** Spacing Method **********************************************************/
		time = (System.currentTimeMillis() - startOfSessionTime
				+ psychologicalTime + lookAhead * 1000) / 1000;

		double[] arrTempAcValue = new double[words.size()];

		for (int i = 0; i < words.size(); i++) {
			
			if ((kActivations[i] + kActivationsOlderSessions[i]) > 0) { // pair
																		// already
																		// seen,
																		// exclude
																		// new
																		// items
				// calculate activation
				if (kActivations[i] > 0) {
					temp_alpha = arrAlpha[i][(kActivations[i] - 1)];
				} else {
					temp_alpha = arrAlphaOlderSessions[i];
				}
				// if last time already shown and
				if ((last_word_seen == i) && (last_answer_correct)) {
					activationValue = 100;

				} else {
					activationValue = getSpacingActivation(i, time, temp_alpha);
					arrTempAcValue[i] = activationValue;
				}
				if (activationValue < lowestActivation) {
					lowestActivation = activationValue;
					index = i;

				}
			}

		}
		if (lowestActivation < threshold) {
			nextPair = index; // repeat the word pair with the lowest activation
								// (at.. seconds from now)

		} else if (words.size() == numberOfPairsShown) { // when all words are
															// shown at least
															// once
			// 2. find the chunck with the oldest repetition
			int k = 0;
			long oldestRep = Long.MAX_VALUE;
			long at;

			for (int i = 0; i < numberOfPairsShown; i++) {
				at = arrActivationsTimeWordOrderedWithOlderSessions[i][(kActivations[i]
						+ kActivationsOlderSessions[i] - 1)];
				if (at < oldestRep) {
					oldestRep = at;
					k = i;
				}
			}
			nextPair = k;
		} else { // 3. show a new wordpair
			if (firstSession) {
				nextPair = numberOfPairsShown;
				numberOfPairsShown++;

			} else {
				for (int i = 0; i < words.size(); i++) {
					if ((kActivations[i] + kActivationsOlderSessions[i]) == 0) {// word
																				// i
																				// already
																				// seen,
																				// exclude
																				// new
																				// items
						nextPair = i;
						last_word_seen = nextPair;
						numberOfPairsShown++;
						return nextPair;
					}
				}
			}
		}
		last_word_seen = nextPair;
		return nextPair;
	}// determineNextPair

	public double [][] getPlot(){
		double [][] a = new double [2][100];
		
		for(int i=0; i<100;i++){
			long j = (System.currentTimeMillis() - startOfSessionTime)/100;
			long k = (j*i)/1000;
			
			a[0][i]=getSpacingActivation(0, k, standardAlpha) ;
			a[1][i]=k;
		}
		
		
		
		
		
		
		
		/*double [][] a = new double [2][kActivations[0]]; 
		
		for(int i =0; i<kActivations[0];i++){
			a[0][i] = arrActivationsTimeWordOrderedWithOlderSessions[0][i];
			a[1][i] = arrActivationValueWithOlderSessions[0][i];
		}*/
		
		 
		return a;
	}
	
	
}// SpacingMethods
